function Graph() {
  this.nodes = null;
  this.lines = null;
  this.nodeWidth = 140;
  this.nodeHeight = 70;
  return this;
}

//清除数据
Graph.prototype.clear = function () {
  this.nodes = null;
  this.lines = null;
};

//设置数据
Graph.prototype.setData = function (data) {
  let nodeData = data || [];
  //缓存节点对象
  let nodes = {};
  for (const i in nodeData) {
    let item = nodeData[i];
    if (!item) {
      continue;
    }
    let node = {};
    node.id = item.id;
    node.pid = item.pid;
    node.width == null && (node.width = this.nodeWidth);
    node.height == null && (node.height = this.nodeHeight);
    // node.width -= Math.floor(Math.random() * 50);
    node.x = 0;
    node.y = 0;
    node.data = item;
    nodes[node.id] = node;
  }
  this.nodes = nodes;
  // console.warn("[setData]", this.nodes, nodeData); //
};

//树形布局
Graph.prototype.layoutOfTree = function (data, option) {
  let tree = {
    isRoot: true,
    id: "TreeRoot",
    width: 0,
    height: 0,
    children: [],
    xRoot: 15,
    yRoot: 15,
    marginX: 60,
    marginY: 15,
    direction: "horizontal", //布局方式:(垂直布局:vertical;水平布局:horizontal)
    center: false, //节点是否居中
    lineType: "curve" //节点连线风格:(直线:straight;曲线:curve )
  };
  if (option) {
    //合并自定义配置
    this.nodeWidth = option.nodeWidth;
    this.nodeHeight = option.nodeHeight;
    tree = Object.assign(tree, option);
  }
  this.clear();
  this.setData(data);
  let nodes = this.nodes;
  for (const i in nodes) {
    let node = nodes[i];
    let pid = node.pid;
    if (pid == null) {
      tree.children.push(node); //根节点
    } else if (nodes[pid]) {
      nodes[pid].children = nodes[pid].children || [];
      nodes[pid].children.push(node);
    }
  }
  this.tree = tree;
  // console.warn("[setLayoutTree]", tree); //
  this.treeLeafTraversal(); //计算树广度
  this.treeRootTraversal(tree); ////计算树深度
  this.setTreeNodePosition(tree); //设置节点坐标
  this.setTreeLines(tree); //设置节点连线
  return this;
};

//计算树节点位置索引(从根节点遍历树)
Graph.prototype.treeRootTraversal = function (root) {
  root.treePath = root.treePath || ""; //节点路径
  root.treeX = root.treeX || 0; //节点位置X
  root.treeY = root.treeY || 0; //节点位置Y
  function traversal(node) {
    let children = node.children;
    if (children) {
      //有子节点
      children.map((item, i) => {
        item.treePath = node.treePath + ".children[" + i + "]"; //节点路径
        item.treeX = i;
        item.treeY = node.treeY + 1;
        traversal(item);
      });
    }
  }
  traversal(root);
  return this;
};

//计算树深度和广度(从叶子节点反向遍历树)
Graph.prototype.treeLeafTraversal = function () {
  let tree = this.tree;
  let nodes = this.nodes;
  let marginX = tree.marginX || 0;
  let marginY = tree.marginY || 0;
  // console.warn("[leafTraversal]", nodes); //
  let leafs = [];
  for (const i in nodes) {
    let node = nodes[i];
    if (node && !node.children) {
      leafs.push(node);
    }
    if (!node.pid) {
      node.pid = "TreeRoot";
    }
  }
  // console.log(leafs); //
  function traversal(items) {
    let parents = [];

    items.map((item) => {
      if (!item.children) {
        item.span = 1; //没有子节点(纯叶子)
        item.deep = 1;
        item.spanWV = item.span * item.width + marginX;
        item.spanWH = item.span * item.height + marginY;
      }
      let parent = nodes[item.pid]; //父节点
      if (item.pid == "TreeRoot") {
        //父节点为根节点
        parent = tree;
      }
      // console.log('[父节点]', parent, item, nodes)
      if (parent && parent.children) {
        //有子节点
        let parentSpan = 0;
        let parentSpanWV = 0;
        let parentSpanWH = 0;
        parent.children.map((item) => {
          let span = item.span || 1;
          parentSpan += span;
          parentSpanWV += item.spanWV;
          parentSpanWH += item.spanWH;
        });
        parent.span = Math.max(parentSpan, 1); //节点最小宽度为1
        parent.spanWV = Math.max(parentSpanWV, parent.width + marginX);
        parent.spanWH = Math.max(parentSpanWH, parent.height + marginY);
        parent.deep = item.deep + 1;
        parents.push(parent);
      }
    });
    parents.length && traversal(parents);
  }
  traversal(leafs);
  return this;
};

//设置节点坐标
Graph.prototype.setTreeNodePosition = function (tree) {
  // console.log('[setTreeNodePosition]', tree); //
  let direction = tree.direction;
  let center = tree.center;
  // let tree = this.tree;
  let marginX = tree.marginX || 0;
  let marginY = tree.marginY || 0;
  let setPos = (node) => {
    let xstart = node.x || 0;
    let ystart = node.y || 0;
    let children = node.children;
    for (const i in children) {
      let child = children[i];
      if (node.isRoot) {
        child.xRoot = xstart;
        child.yRoot = ystart;
      } else {
        child.xRoot = node.xRoot;
        child.yRoot = node.yRoot;
      }
      let width = child.width + marginX;
      let height = child.height + marginY;
      switch (direction) {
        case "horizontal": //水平布局
          child.x = child.xRoot + (child.treeY - 1) * width;
          child.y = ystart;
          if (center) {
            child.y -= (node.spanWH - child.span * height) / 2;
          }
          break;
        case "vertical":
          //  垂直布局
          child.y = child.yRoot + (child.treeY - 1) * height;
          child.x = xstart;
          if (center) {
            child.x -= (node.spanWV - child.span * width) / 2;
          }
          break;
      }
      xstart = xstart + child.span * width;
      ystart = ystart + child.span * height;
      setPos(child);
    }
  };
  tree.x = tree.xRoot || 0;
  tree.y = tree.yRoot || 0;
  if (center) {
    switch (direction) {
      case "horizontal": //  水平布局
        tree.y += tree.spanWH / 2;
        break;
      case "vertical": //  垂直布局
        tree.x += tree.spanWV / 2;
        break;
    }
  }
  setPos(tree);
  return;
};
//设置节点连线
Graph.prototype.setTreeLines = function (tree) {
  let nodes = this.nodes;
  // console.log("[setLines]", nodes); //
  let direction = tree.direction;
  let lineType = tree.lineType;
  let lineStyle = lineType + "&" + direction; //节点连线风格
  let lines = {};
  for (const i in nodes) {
    let node = nodes[i];
    let id = node.id + "";
    let parent = nodes[node.pid];

    if (parent) {
      let source = {
        width: parent.width,
        height: parent.height,
        x: parent.x,
        y: parent.y
      };
      let target = {
        width: node.width,
        height: node.height,
        x: node.x,
        y: node.y
      };

      let path = "";

      switch (lineStyle) {
        case "curve&horizontal": //曲线连线-水平布局
          {
            let x = source.x + source.width;
            let y = source.y + source.height / 2;

            let xend = target.x;
            let yend = target.y + target.height / 2;

            let xm = (xend + x) / 2; //中点
            let ym = (yend + y) / 2;

            let cx = x + (xm - x) * 0.7; //控制点
            let cy = y + (ym - y) * 0.15;

            let x1 = xend;
            let y1 = yend;

            let cx1 = xm + (x1 - xm) * 0.3; //控制点
            let cy1 = ym + (y1 - ym) * 0.85;

            //曲线结尾marker偏移
            let dis = 10 / Math.sqrt(Math.pow(x1 - cx1, 2) + Math.pow(y1 - cy1, 2));
            x1 = xend - dis * (xend - cx1);
            y1 = yend - dis * (yend - cy1);

            path = `M${x},${y} Q${cx},${cy} ${xm},${ym} Q${cx1},${cy1} ${x1},${y1} `;
          }
          break;
        case "curve&vertical": //曲线连线-垂直布局
          {
            let x = source.x + source.width / 2;
            let y = source.y + source.height;

            let xend = target.x + source.width / 2;
            let yend = target.y;

            let xm = (x + xend) / 2; //中点
            let ym = (y + yend) / 2;

            let cx = x + (xm - x) * 0.15; //控制点
            let cy = y + (ym - y) * 0.7;

            let x1 = xend;
            let y1 = yend;

            let cx1 = xm + (x1 - xm) * 0.85; //控制点
            let cy1 = ym + (y1 - ym) * 0.3;

            //曲线结尾marker偏移
            let dis = 10 / Math.sqrt(Math.pow(x1 - cx1, 2) + Math.pow(y1 - cy1, 2));
            x1 = xend - dis * (xend - cx1);
            y1 = yend - dis * (yend - cy1);

            path = `M${x},${y} Q${cx},${cy} ${xm},${ym} Q${cx1},${cy1} ${x1},${y1} `;

            // let x1 = target.x + source.width / 2;
            // let y1 = target.y;
            // let cx = x + (x1 - x) * .1;
            // let cy = y + (y1 - y) * 0.7;
            // let cx1 = x + (x1 - x) * .9;
            // let cy1 = y + (y1 - y) * 0.3;
            // let cx2 = x1;
            // let cy2 = y1;
            // //曲线结尾marker偏移
            // let dis = 10 / Math.sqrt(Math.pow(x1 - cx1, 2) + Math.pow(y1 - cy1, 2));
            // x1 = x1 - dis * (x1 - cx1);
            // y1 = y1 - dis * (y1 - cy1);
            // path = `M ${x} ${y} C${cx} ${cy} ${cx1} ${cy1} ${cx2} ${cy2} `;
          }
          break;
        default:
          // 包围盒直线连线(不需区分水平还是垂直)
          {
            let p = this.getLinePoint(source, target);
            let x = p.x;
            let y = p.y;
            let x1 = p.x1;
            let y1 = p.y1;
            let xm = (x + x1) / 2;
            let ym = (y + y1) / 2;
            path = `M ${x} ${y} L${xm} ${ym} L${x1} ${y1}`;
          }
          break;
      }
      let lineId = parent.id + "@" + id;
      lines[lineId] = {
        source: parent.id,
        target: id,
        d: path
        // arrowEnd: pathEnd
      };
    }
  }
  this.lines = lines; //节点连线
  return this;
};

//获取节点连线坐标
Graph.prototype.getLinePoint = function (source, target) {
  // console.warn("=======", source, target);
  //根据2个节点获取连线
  if (!source || !target) {
    console.warn("节点为空source,target", source, target);
  }
  // if (!source.hasOwnProperty() || !source.y || !target.x || !target.y) {
  //   console.warn("连线节点坐标为空[source,target]", source, target);
  // }
  var sourceRX = source.width / 2; //source 节点 x轴半径
  var sourceRY = source.height / 2; //source 节点 y轴半径
  var sourceCenterX = source.x + sourceRX; // source 中心点 x
  var sourceCenterY = source.y + sourceRY; // source 中心点 y

  var targetRX = target.width / 2; //target 节点 x轴半径
  var targetRY = target.height / 2; //target 节点 y轴半径
  var targetCenterX = target.x + targetRX; // target 中心点 x
  var targetCenterY = target.y + targetRY; // target 中心点 y

  var v = {
    x: targetCenterX - sourceCenterX, // 向量x
    y: targetCenterY - sourceCenterY, // 向量y
    x1: sourceCenterX - targetCenterX, // 向量x
    y1: sourceCenterY - targetCenterY, // 向量y
    centerX: (sourceCenterX + targetCenterX) / 2, // 中点x
    centerY: (sourceCenterY + targetCenterY) / 2 // 中点y
  };
  v.dis = Math.sqrt(v.x * v.x + v.y * v.y); // 2点距离

  //默认矩形节点
  var x = sourceCenterX + (v.x > 0 ? sourceRX : -sourceRX);
  var y = sourceCenterY + (sourceRX / Math.abs(v.x)) * v.y;
  if (y < source.y || y > source.y + source.height) {
    y = sourceCenterY + (v.y > 0 ? sourceRY : -sourceRY);
    x = sourceCenterX + (sourceRY / Math.abs(v.y)) * v.x;
  }
  if (source.type == "circle") {
    //圆形节点Y
    x = sourceCenterX + (sourceRX / v.dis) * v.x;
    y = sourceCenterY + (sourceRY / v.dis) * v.y;
  }
  //默认矩形节点
  var x1 = targetCenterX + (v.x1 > 0 ? targetRX : -targetRX);
  var y1 = targetCenterY + (targetRX / Math.abs(v.x1)) * v.y1;
  if (y1 < target.y || y1 > target.y + target.height) {
    y1 = targetCenterY + (v.y1 > 0 ? targetRY : -targetRY);
    x1 = targetCenterX + (targetRY / Math.abs(v.y1)) * v.x1;
  }
  if (target.type == "circle") {
    //圆形节点Y
    x1 = targetCenterX + (targetRX / v.dis) * v.x1;
    y1 = targetCenterY + (targetRY / v.dis) * v.y1;
  }
  let linePos = {
    x: Math.floor(x),
    y: Math.floor(y),
    x1: Math.floor(x1),
    y1: Math.floor(y1),
    sourceX: Math.floor(sourceCenterX),
    sourceY: Math.floor(sourceCenterY),
    targetX: Math.floor(targetCenterX),
    targetY: Math.floor(targetCenterY)
  };
  // console.error(linePos);
  return linePos;
};

Graph.prototype.getLinePath = function (source, target, lineStyle) {
  let path = "";
  if (source && target) {
    switch (lineStyle) {
      case "curve&horizontal": {
        //曲线连线-水平布局

        let x = +source.x + source.width;
        let y = +source.y + source.height / 2;

        let xend = +target.x;
        let yend = +target.y + target.height / 2;

        let xm = (xend + x) / 2; //中点
        let ym = (yend + y) / 2;

        let cx = x + (xm - x) * 0.7; //控制点
        let cy = y + (ym - y) * 0.15;

        let x1 = xend;
        let y1 = yend;

        let cx1 = xm + (x1 - xm) * 0.3; //控制点
        let cy1 = ym + (y1 - ym) * 0.85;

        //曲线结尾marker偏移
        let dis = 10 / Math.sqrt(Math.pow(x1 - cx1, 2) + Math.pow(y1 - cy1, 2));
        x1 = xend - dis * (xend - cx1);
        y1 = yend - dis * (yend - cy1);

        path = `M${x},${y} Q${cx},${cy} ${xm},${ym} Q${cx1},${cy1} ${x1},${y1} `;
        break;
      }

      case "curve&vertical": {
        //曲线连线-垂直布局

        let x = +source.x + source.width / 2;
        let y = +source.y + source.height;

        let xend = +target.x + source.width / 2;
        let yend = +target.y;

        let xm = (x + xend) / 2; //中点
        let ym = (y + yend) / 2;

        let cx = x + (xm - x) * 0.15; //控制点
        let cy = y + (ym - y) * 0.7;

        let x1 = xend;
        let y1 = yend;

        let cx1 = xm + (x1 - xm) * 0.85; //控制点
        let cy1 = ym + (y1 - ym) * 0.3;

        //曲线结尾marker偏移
        let dis = 10 / Math.sqrt(Math.pow(x1 - cx1, 2) + Math.pow(y1 - cy1, 2));
        x1 = xend - dis * (xend - cx1);
        y1 = yend - dis * (yend - cy1);

        path = `M${x},${y} Q${cx},${cy} ${xm},${ym} Q${cx1},${cy1} ${x1},${y1} `;

        // let x1 = target.x + source.width / 2;
        // let y1 = target.y;
        // let cx = x + (x1 - x) * .1;
        // let cy = y + (y1 - y) * 0.7;
        // let cx1 = x + (x1 - x) * .9;
        // let cy1 = y + (y1 - y) * 0.3;
        // let cx2 = x1;
        // let cy2 = y1;
        // //曲线结尾marker偏移
        // let dis = 10 / Math.sqrt(Math.pow(x1 - cx1, 2) + Math.pow(y1 - cy1, 2));
        // x1 = x1 - dis * (x1 - cx1);
        // y1 = y1 - dis * (y1 - cy1);
        // path = `M ${x} ${y} C${cx} ${cy} ${cx1} ${cy1} ${cx2} ${cy2} `;
        break;
      }
      default: {
        // 包围盒直线连线(不需区分水平还是垂直
        let p = this.getLinePoint(source, target);
        let x = p.x;
        let y = p.y;
        let x1 = p.x1;
        let y1 = p.y1;
        let xm = (x + x1) / 2;
        let ym = (y + y1) / 2;
        //曲线结尾marker偏移
        let dis = 9 / Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2));
        x1 = x1 - dis * (x1 - x);
        y1 = y1 - dis * (y1 - y);
        path = `M ${x} ${y} L${xm} ${ym} L${x1} ${y1}`;
        break;
      }
    }
  }
  return path;
};

//获取节点连线坐标
Graph.prototype.getLinePoint1 = function (source, target) {
  // console.warn("=======", source, target);
  //根据2个节点获取连线
  if (!source || !target) {
    console.warn("节点为空source,target", source, target);
  }

  var sourceRX = source.width / 2; //source 节点 x轴半径
  var sourceRY = source.height / 2; //source 节点 y轴半径
  var sourceCenterX = source.x + sourceRX; // source 中心点 x
  var sourceCenterY = source.y + sourceRY; // source 中心点 y

  var targetRX = target.width / 2; //target 节点 x轴半径
  var targetRY = target.height / 2; //target 节点 y轴半径
  var targetCenterX = target.x + targetRX; // target 中心点 x
  var targetCenterY = target.y + targetRY; // target 中心点 y

  let x = sourceCenterX;
  let y = sourceCenterY;
  let x1 = targetCenterX;
  let y1 = targetCenterY;

  let dx = targetCenterX - sourceCenterX;
  let dy = targetCenterY - sourceCenterY;

  // let dxAbs = Math.abs(Math.abs(dx) - sourceRX - targetRX);
  // let dyAbs = Math.abs(Math.abs(dy) - sourceRY - targetRY);
  let dxAbs = Math.abs(Math.abs(dx) - sourceRX - targetRX);
  let dyAbs = Math.abs(Math.abs(dy) - sourceRY - targetRY);
  if (dx >= 0) {
    //target 右侧
    let isConnectRight = dyAbs < dxAbs && Math.abs(dx) - sourceRX - targetRX >= 0;
    isConnectRight = isConnectRight || Math.abs(dy) - sourceRY - targetRY <= 0;

    if (isConnectRight) {
      x = sourceCenterX + sourceRX;
      x1 = targetCenterX - targetRX;
    } else if (dy > 0) {
      //
      y = sourceCenterY + sourceRY;
      y1 = targetCenterY - targetRY;
    } else {
      y = sourceCenterY - sourceRY;
      y1 = targetCenterY + targetRY;
    }
  } else {
    //target 左侧

    let isConnectLeft = dyAbs < dxAbs && Math.abs(dx) - sourceRX - targetRX >= 0;
    isConnectLeft = isConnectLeft || Math.abs(dy) - sourceRY - targetRY <= 0;

    if (isConnectLeft) {
      x = sourceCenterX - sourceRX;
      x1 = targetCenterX + targetRX;
    } else if (dy > 0) {
      //
      y = sourceCenterY + sourceRY;
      y1 = targetCenterY - targetRY;
    } else {
      y = sourceCenterY - sourceRY;
      y1 = targetCenterY + targetRY;
    }
  }

  let linePos = {
    x: Math.floor(x),
    y: Math.floor(y),
    x1: Math.floor(x1),
    y1: Math.floor(y1),

    sourceX: Math.floor(sourceCenterX),
    sourceY: Math.floor(sourceCenterY),
    targetX: Math.floor(targetCenterX),
    targetY: Math.floor(targetCenterY)
  };
  linePos.centerX = Math.floor((x + x1) / 2); // 中点x
  linePos.centerY = Math.floor((y + y1) / 2); // 中点y
  // console.error(linePos);
  return linePos;
};

//获取2点之间的连线
Graph.prototype.getLine = function (source, target, lineStyle) {
  if (!source || !target) {
    return null;
  }
  let line = { source, target, lineStyle };
  switch (lineStyle) {
    case "curve": {
      //曲线连线-水平布局
      let p = this.getLinePoint1(source, target);
      let x = p.x;
      let y = p.y;
      let x1 = p.x1;
      let y1 = p.y1;

      let dx = x1 - x;
      let dy = y1 - y;
      // //连线结尾marker偏移

      let dir = Math.abs(dy / dx) > 0.6 && Math.abs(dy / dx) < 3 ? 10 : 7;
      let dis = dir / Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
      x1 = x1 - dis * (x1 - x);
      y1 = y1 - dis * (y1 - y);

      dx = x1 - x;
      dy = y1 - y;

      let xm = (x + x1) / 2;
      let ym = (y + y1) / 2;

      line.centerX = xm;
      line.centerY = ym;

      let percentC = 0.15;
      let percentC1 = 0.9;

      let percentC2 = 0.15;
      let percentC3 = 0.9;

      let cx = x + (xm - x) * percentC; //控制点
      let cy = y + (ym - y) * percentC1;

      let cx1 = xm + (x1 - xm) * percentC3; //控制点
      let cy1 = ym + (y1 - ym) * percentC2;

      if ((dx > 0 && p.x1 < p.targetX) || (dx < 0 && p.x1 > p.targetX)) {
        //左右连线
        cx = x + (xm - x) * percentC3; //控制点
        cy = y + (ym - y) * percentC2;

        cx1 = xm + (x1 - xm) * percentC; //控制点
        cy1 = ym + (y1 - ym) * percentC1;
      }

      line.path = `M${x},${y} C${cx},${cy} ${cx1},${cy1} ${x1},${y1}  `;

      break;
    }
    default: {
      // 包围盒直线连线(不需区分水平还是垂直
      let p = this.getLinePoint(source, target);
      let x = p.x;
      let y = p.y;
      let x1 = p.x1;
      let y1 = p.y1;
      let xm = (x + x1) / 2;
      let ym = (y + y1) / 2;
      //连线结尾marker偏移
      let dis = 7 / Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2));
      x1 = x1 - dis * (x1 - x);
      y1 = y1 - dis * (y1 - y);
      line.centerX = xm;
      line.centerY = ym;
      line.path = `M ${x} ${y} L${xm} ${ym} L${x1} ${y1}`;
      break;
    }
  }
  return line;
};
//获取连线
Graph.prototype.getLines = function (nodes) {
  nodes = nodes || {};
  let lines = {};
  for (const i in nodes) {
    let target = nodes[i];
    target.width = 140;
    target.height = 70;
    target.x -= 0;
    target.y -= 0;
    let pidList = target.pidList || [];
    let targetId = target.pid;
    for (const j in pidList) {
      let sourceId = pidList[j];
      let source = nodes[sourceId];
      if (!source) {
        console.warn("[节点连线找不到源节点source]", nodes);
        continue;
      }
      source.width = 140;
      source.height = 70;
      source.x -= 0;
      source.y -= 0;
      let lineId = sourceId + "@" + targetId;
      lines[lineId] = {
        source: source,
        target: target,
        d: this.getLinePath(source, target)
      };
    }
  }
  // console.log("[getLines]", nodes, lines);
  return lines;
};
export default Graph;
